﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SlimDX;

namespace System.Windows.Media.Media3D10
{
    /// <summary>
    /// Permet l'assignation des informations relatives à la source lumineuse dans l'effet associé
    /// </summary>
    public sealed class LightPositionEffectParameter : EffectParameter
    {
        #region Dependency Properties
        /// <summary>
        /// Identifie la propriété de dépendance <see cref="ApplyLocalPosition"/>.
        /// </summary>
        public static readonly DependencyProperty ApplyLocalPositionProperty =
            DependencyProperty.Register("ApplyLocalPosition", typeof(bool), typeof(LightPositionEffectParameter));

        /// <summary>
        /// Identifie la propriété de dépendance <see cref="ApplyWorldPosition"/>.
        /// </summary>
        public static readonly DependencyProperty ApplyWorldMatrixProperty =
            DependencyProperty.Register("ApplyWorldPosition", typeof(bool), typeof(LightPositionEffectParameter));

        /// <summary>
        /// Identifie la propriété de dépendance <see cref="ApplyViewPosition"/>.
        /// </summary>
        public static readonly DependencyProperty ApplyViewMatrixProperty =
            DependencyProperty.Register("ApplyViewPosition", typeof(bool), typeof(LightPositionEffectParameter));

        /// <summary>
        /// Identifie la propriété de dépendance <see cref="ApplyProjectionPosition"/>.
        /// </summary>
        public static readonly DependencyProperty ApplyProjectionPositionProperty =
            DependencyProperty.Register("ApplyProjectionPosition", typeof(bool), typeof(LightPositionEffectParameter));
        #endregion

        #region Data
        private const string g_vLightPositionLocal = "g_vLightPositionLocal";
        private const string g_vLightPositionWorld = "g_vLightPositionWorld";
        private const string g_vLightPositionView = "g_vLightPositionView";
        private const string g_vLightPositionProjection = "g_vLightPositionProjection";

        private SlimDX.Direct3D10.EffectVectorVariable _lightPositionLocal;
        private SlimDX.Direct3D10.EffectVectorVariable _lightPositionWorld;
        private SlimDX.Direct3D10.EffectVectorVariable _lightPositionView;
        private SlimDX.Direct3D10.EffectVectorVariable _lightPositionProjection;
        #endregion

        #region Public Properties
        /// <summary>
        /// Obtient ou défini une valeur indiquant si la position de la lumière dans l'espace objet doit être appliquée
        /// </summary>
        public bool ApplyLocalPosition
        {
            get { return (bool)GetValue(ApplyLocalPositionProperty); }
            set { SetValue(ApplyLocalPositionProperty, value); }
        }

        /// <summary>
        /// Obtient ou défini une valeur indiquant si la position de la lumière dans l'espace monde doit être appliquée
        /// </summary>
        public bool ApplyWorldPosition
        {
            get { return (bool)GetValue(ApplyWorldMatrixProperty); }
            set { SetValue(ApplyWorldMatrixProperty, value); }
        }

        /// <summary>
        /// Obtient ou défini une valeur indiquant si la position de la lumière dans l'espace vue doit être appliquée
        /// </summary>
        public bool ApplyViewPosition
        {
            get { return (bool)GetValue(ApplyViewMatrixProperty); }
            set { SetValue(ApplyViewMatrixProperty, value); }
        }

        /// <summary>
        /// Obtient ou défini une valeur indiquant si la position de la lumière dans l'espace de projection doit être appliquée
        /// </summary>
        public bool ApplyProjectionPosition
        {
            get { return (bool)GetValue(ApplyProjectionPositionProperty); }
            set { SetValue(ApplyProjectionPositionProperty, value); }
        }
        #endregion

        #region EffectParameter Implementation
        /// <summary>
        /// Appelé lors de l'initialisation de l'effet associé
        /// </summary>
        /// <param name="effect">Effet associé</param>
        protected override void OnInitEffect(SlimDX.Direct3D10.Effect effect)
        {
            base.OnInitEffect(effect);
            if (effect != null)
            {
                _lightPositionLocal = effect.GetVariableByName(g_vLightPositionLocal).AsVector();
                _lightPositionWorld = effect.GetVariableByName(g_vLightPositionWorld).AsVector();
                _lightPositionView = effect.GetVariableByName(g_vLightPositionView).AsVector();
                _lightPositionProjection = effect.GetVariableByName(g_vLightPositionProjection).AsVector();
            }
            else
            {
                _lightPositionLocal = null;
                _lightPositionWorld = null;
                _lightPositionView = null;
                _lightPositionProjection = null;
            }
        }

        /// <summary>
        /// En cas de substitution dans une classe dérivée, participe au dessin des objets dans le pipeline DirectX10
        /// </summary>
        /// <param name="context">Contexte de dessin.</param>
        /// <exception cref="ArgumentNullException">Soulevée si le contexte est null.</exception>
        protected override void OnDraw(DrawingContext context)
        {
            if (object.ReferenceEquals(context, null) 
                || object.ReferenceEquals(context.Light, null))
                throw new ArgumentNullException("context");

            if (ApplyLocalPosition || ApplyWorldPosition || ApplyViewPosition || ApplyProjectionPosition)
            {
                Vector3 worldPosition;
                PointLightBase pointLight = context.Light as PointLightBase;
                if (pointLight != null)
                {
                    if (object.ReferenceEquals(context.Light.Transform, null))
                        worldPosition = pointLight.Position;
                    else
                        worldPosition = context.Light.Transform.Transform(pointLight.Position);
                }
                else
                {
                    if (object.ReferenceEquals(context.Light.Transform, null))
                        worldPosition = context.Light.Transform.Transform(Vector3.Zero);
                    else
                        worldPosition = Vector3.Zero;
                }
                if (this.ApplyLocalPosition)
                {
                    Vector3 objectPosition = Vector3.TransformCoordinate(Vector3.Zero, context.WorldMatrix);
                    Vector3 localPosition = worldPosition - objectPosition;
                    _lightPositionLocal.Set(localPosition);
                }
                if (this.ApplyWorldPosition)
                {
                    _lightPositionWorld.Set(worldPosition);
                }
                if (this.ApplyViewPosition)
                {
                    Vector3 viewPosition = Vector3.TransformCoordinate(worldPosition, context.ViewMatrix);
                    _lightPositionView.Set(viewPosition);
                }
                if (this.ApplyProjectionPosition)
                {
                    SlimDX.Matrix vpMatrix = SlimDX.Matrix.Multiply(context.ViewMatrix, context.ProjectionMatrix);
                    Vector3 projectionPosition = Vector3.TransformCoordinate(worldPosition, vpMatrix);
                    _lightPositionProjection.Set(projectionPosition);
                }
            }
        }
        #endregion

        #region Freezable Implementation
        /// <summary>
        /// Une fois implémentée dans une classe dérivée, initialise une nouvelle instance de la classe dérivée <see cref="Freezable"/>. 
        /// </summary>
        /// <returns>Nouvelle instance.</returns>
        protected override Freezable CreateInstanceCore()
        {
            return new LightPositionEffectParameter();
        }
        #endregion
    }
}
